#pragma once

#include "../Core/IDisposable.h"
#include "../Core/RectangleTessellator.h"
#include "../Core/Mesh.h"

#include "../Renderer/Context.h"
#include "../Renderer/Device.h"
#include "../Renderer/DrawCommand.h"
#include "../Renderer/ShaderProgram.h"
#include "../Renderer/SceneState.h"
#include "../Renderer/Texture2D.h"
#include "../Renderer/Texture2DDescription.h"
#include "../Renderer/Uniform.h"
#include "../Renderer/WritePixelBuffer.h"
#include "../Renderer/WritePixelBufferGL3x.h"

#include "TerrainTile.h"

using namespace Core;
using namespace Renderer;

namespace Scene
{
	enum TerrainNormalsAlgorithm
	{
		None,
		ForwardDifference,
		CentralDifference,
		SobelFilter
	};

	enum TerrainShadingAlgorithm
	{
		ColorMap,
		Solid,
		ByHeight,
		HeightContour,
		ColorRampByHeight,
		BlendRampByHeight,
		BySlope,
		SlopeContour,
		ColorRampBySlope,
		BlendRampBySlope,
		BlendMask
	};

	class VertexDisplacementMapTerrainTile : public IDisposable
	{
	public:
		VertexDisplacementMapTerrainTile(Context* context, const TerrainTile* tile)
		{
			auto device = Device::GetInstance();

			auto pixelBuffer = device->CreateWritePixelBuffer(PixelBufferHint::Stream,
				sizeof(float)*tile->GetLenght());
			//TODO 
			auto pixelBufferGL3x = (Renderer::GL3x::WritePixelBufferGL3x*)pixelBuffer;
			pixelBufferGL3x->CopyFromSystemMemory<float>(tile->GetHeights(), 0, sizeof(float)*tile->GetLenght());
		
			_texture = device->CreateTexture2DRectangle(Texture2DDescription(
				tile->GetResolution().x, tile->GetResolution().y, TextureFormat::Red32f));
			_texture->CopyFromBuffer(pixelBuffer, ImageFormat::Red, ImageDatatype::Float);

			/////////////////////////////////////////////////////////////////////////////

			auto spTerraim = device->CreateShaderProgramFromFile("shaders/TerrainVS.glsl", "shaders/TerrainFS.glsl");
			_heightExaggerationUniform = (TemplateUniform<float>*)spTerraim->GetUniforms()->Get("u_heightExaggeration");
			((TemplateUniform<Vector2F>*)spTerraim->GetUniforms()->Get("u_positionToTextureCoordinate"))->SetValue(Vector2F(
				(float)(1.0 / (double)(tile->GetResolution().x)),
				(float)(1.0 / (double)(tile->GetResolution().y))
				));
			((TemplateUniform<Vector2F>*)spTerraim->GetUniforms()->Get("u_positionToRepeatTextureCoordinate"))->SetValue(Vector2F(
				(float)(4.0 / (double)(tile->GetResolution().x)),
				(float)(4.0 / (double)(tile->GetResolution().y))
				));

			//////////////////////////////////////////////////////////////////////////
			auto mesh = RectangleTessellator::Compute(RectangleD(
				Vector2D(0.5, 0.5),
				Vector2D((double)tile->GetResolution().x - 0.5, (double)tile->GetResolution().y - 0.5)),
				tile->GetResolution().x - 1,
				tile->GetResolution().y - 1);

			_va = context->CreateVertexArray(mesh, spTerraim->GetVertexAttributes(), BufferHint::StaticDraw);
			_primitiveType = mesh->GetPrimitiveType();

			//////////////////////////////////////////////////////////////////////////

			RenderState* rsTerrain = new RenderState();
			rsTerrain->FacetCulling.FrontFaceWindingOrder = mesh->GetFrontFaceWindingOrder();
		
			///////////////////////////////////////////////////////////////////////////
			_drawStateTerrain = new DrawCommand(rsTerrain, spTerraim, _va);

			_tileMinimumHeight = tile->GetMinimumHeight();
			_tileMaximumHeight = tile->GetMaximumHeight();

			_heightExaggeration = 30;
			_normalsAlgorithm = TerrainNormalsAlgorithm::ForwardDifference;
			_showTerrain = true;
			_dirty = true;
		}

		void Render(Context* context, SceneState* sceneState)
		{
			if (_showTerrain || _showSilhouette)
			{
				Update(sceneState);

				auto textureUnits = context->GetTextureUnits();

				auto linearClamp = Device::GetInstance()->GetTextureSamplers()->GetLinearClamp();
				auto nearestClamp = Device::GetInstance()->GetTextureSamplers()->GetNearestClamp();
				auto linearRepeat = Device::GetInstance()->GetTextureSamplers()->GetLinearRepear();

				textureUnits->GetTextureUnitAt(0)->SetTexture(_texture);
				textureUnits->GetTextureUnitAt(0)->SetTextureSampler(nearestClamp);

				textureUnits->GetTextureUnitAt(6)->SetTexture(_colorMapTexture);
				textureUnits->GetTextureUnitAt(6)->SetTextureSampler(linearClamp);

				textureUnits->GetTextureUnitAt(1)->SetTexture(_colorRampHeightTexture);
				textureUnits->GetTextureUnitAt(1)->SetTextureSampler(linearClamp);

				textureUnits->GetTextureUnitAt(7)->SetTexture(_colorRampSlopeTexture);
				textureUnits->GetTextureUnitAt(7)->SetTextureSampler(linearClamp);

				textureUnits->GetTextureUnitAt(2)->SetTexture(_BlendRampTexture);
				textureUnits->GetTextureUnitAt(2)->SetTextureSampler(linearClamp);

				textureUnits->GetTextureUnitAt(3)->SetTexture(_grassTexture);
				textureUnits->GetTextureUnitAt(3)->SetTextureSampler(linearRepeat);

				textureUnits->GetTextureUnitAt(4)->SetTexture(_stoneTexture);
				textureUnits->GetTextureUnitAt(4)->SetTextureSampler(linearRepeat);

				textureUnits->GetTextureUnitAt(5)->SetTexture(_blendMaskTexture);
				textureUnits->GetTextureUnitAt(5)->SetTextureSampler(linearClamp);

				if (_showTerrain || _showSilhouette)
				{
					context->Draw(_primitiveType, _drawStateTerrain, sceneState);
				}
			}
		}

		
		void SetColorMapTexture(Texture2D* texture)
		{
			_colorMapTexture = texture;
		}
		void SetColorRampHeightTexture(Texture2D* texture)
		{
			_colorRampHeightTexture = texture;
		}
		void SetColorRampSlopeTexture(Texture2D* texture)
		{
			_colorRampSlopeTexture = texture;
		}
		void SetBlendRampTexture(Texture2D* texture)
		{
			_BlendRampTexture = texture;
		}
		void SetGrassTexture(Texture2D* texture)
		{
			_grassTexture = texture;
		}
		void SetStoneTexture(Texture2D* texture)
		{
			_stoneTexture = texture;
		}
		void SetBlendMaskTexture(Texture2D* texture)
		{
			_blendMaskTexture = texture;
		}


		void Destroy() override
		{
			_drawStateTerrain->GetShaderProgram()->Destroy();

			_va->Destroy();
			_texture->Destroy();
		}

	private:
		void Update(SceneState* sceneState)
		{
			if (_dirty)
			{
				auto terrainSP = _drawStateTerrain->GetShaderProgram();
				((TemplateUniform<int>*)terrainSP->GetUniforms()->Get("u_normalAlgorithm"))->SetValue((int)_normalsAlgorithm);
				((TemplateUniform<int>*)terrainSP->GetUniforms()->Get("u_shadingAlgorithm"))->SetValue((int)_shadingAlgorithm);
				((TemplateUniform<bool>*)terrainSP->GetUniforms()->Get("u_showTerrain"))->SetValue(_showTerrain);
				((TemplateUniform<bool>*)terrainSP->GetUniforms()->Get("u_showSilhouette"))->SetValue(_showSilhouette);
				((TemplateUniform<int>*)terrainSP->GetUniforms()->Get("u_normalAlgorithm"))->SetValue((int)_normalsAlgorithm);

				_minimumHeight = ((TemplateUniform<float>*)terrainSP->GetUniforms()->Get("u_minimumHeight"));
				_maximumHeight = ((TemplateUniform<float>*)terrainSP->GetUniforms()->Get("u_maximumHeight"));

				_dirty = false;
			}

			_heightExaggerationUniform->SetValue(_heightExaggeration);
			_minimumHeight->SetValue(_tileMinimumHeight * _heightExaggeration);
			_maximumHeight->SetValue(_tileMaximumHeight * _heightExaggeration);
		}

	private:
		DrawCommand* _drawStateTerrain;
		TemplateUniform<float>* _heightExaggerationUniform;
		TemplateUniform<float>* _minimumHeight;
		TemplateUniform<float>* _maximumHeight;

		Texture2D* _texture;
		VertexArray* _va;
		PrimitiveType _primitiveType;

		Texture2D* _colorMapTexture;
		Texture2D* _colorRampHeightTexture;
		Texture2D* _colorRampSlopeTexture;
		Texture2D* _BlendRampTexture;
		Texture2D* _grassTexture;
		Texture2D* _stoneTexture;
		Texture2D* _blendMaskTexture;


		float _tileMinimumHeight;
		float _tileMaximumHeight;

		float _heightExaggeration;
		TerrainNormalsAlgorithm _normalsAlgorithm;
		TerrainShadingAlgorithm _shadingAlgorithm;
		bool _showTerrain;
		bool _showSilhouette;
		bool _dirty;
	};
}